﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Forms;
using SemenQuest.Communication;
using System.Threading;

namespace SemenQuest.Models
{
    public class Egg : Agent
    {
        private System.Timers.Timer LotteryTimer;
        private System.ComponentModel.IContainer components;

        #region Variables
        private class Impregnator
        {
            public Impregnator(Guid Id, Int32 Position)
            {
                this.Id = Id; this.Position = Position;
            }
            public Guid Id;
            public Int32 Position;
            public Int32 LotteryNumbers;
        }

        public Int32 SemenAttached { get { return Impregnators.Count; } }
        public Int32 MaxImpregnators { get; private set; }
        private List<Impregnator> Impregnators { get; set; }
        private Boolean LotteryStarted { get; set; }
        private Dictionary<Int32, Boolean> ImpregLocations { get; set; }

        private static Image[] AnimationImages { get; set; }
        private static Boolean StaticSettingsLoaded { get; set; }
        private static Int32 ImpregnatorsNeeded { get; set; }
        #endregion

        #region Constructor
        private void InitializeComponent()
        {
            this.LotteryTimer = new System.Timers.Timer();
            ((System.ComponentModel.ISupportInitialize)(this.LotteryTimer)).BeginInit();
            this.SuspendLayout();
            // 
            // LotteryTimer
            // 
            this.LotteryTimer.AutoReset = false;
            this.LotteryTimer.Interval = 5000D;
            this.LotteryTimer.SynchronizingObject = this;
            this.LotteryTimer.Elapsed += new System.Timers.ElapsedEventHandler(this.LotteryDone);
            ((System.ComponentModel.ISupportInitialize)(this.LotteryTimer)).EndInit();
            this.ResumeLayout(false);

        }
        public Egg()
            : base()
        {
            InitializeComponent();
            LoadStaticSettings();

            #region Load Settings
            MaxVelocity = Properties.Settings.Default.EggMaxVelocity;
            Visibility = Properties.Settings.Default.EggVisibility;
            TurnSpeed = Properties.Settings.Default.EggTurnSpeed;
            Acceleration = Properties.Settings.Default.EggAcceleration;
            BreakSpeed = Properties.Settings.Default.EggBreakSpeed;
            LifeTime = Properties.Settings.Default.EggLifeTime;
            ImpregnatorsNeeded = Properties.Settings.Default.EggImpregnatorsNeeded;
            #endregion
            SettingsController.EggSettingChanged += Agent_SettingsUpdate;

            MaxImpregnators = 4;
            Impregnators = new List<Impregnator>();
            ImpregLocations = new Dictionary<Int32, Boolean>();
            for (Int32 i = 0; i < MaxImpregnators; i++)
            {
                ImpregLocations.Add(i, false);
            }

            Name = "Egg";
            AgentType = AgentTypes.Egg;
            Angle = new Random(DateTime.Now.Millisecond).Next(0, 360);
            Velocity = MaxVelocity;

            AnimationImages = GetImages(Properties.Settings.Default.EggColor);

            Size = new Size(
                (Int32)(AnimationImages[0].Size.Width * 1.3),
                (Int32)(AnimationImages[0].Size.Height * 1.3));

            NewMessage += HandleMessage;

            TickSubscribe(Move);
            TickSubscribe(Main);
        }
        [MethodImpl(MethodImplOptions.Synchronized)]
        private static void LoadStaticSettings()
        {
            if (StaticSettingsLoaded)
                return;

            SettingsController.EggSettingChanged += Egg_SettingsUpdate;
            StaticSettingsLoaded = true;
        }
        private static void Egg_SettingsUpdate(String SettingName, Object value)
        {
            if (SettingName == "EggImpregnatorsNeeded")
            {
                ImpregnatorsNeeded = (Int32)value;
            }
            else if (SettingName == "EggColor")
            {
                AnimationImages = GetImages((Color)value);
            }
        }
        /// <summary>
        /// Change the color of a Image
        /// </summary>
        /// <param name="color"></param>
        /// <returns></returns>
        private static Image[] GetImages(Color color)
        {
            String oldColorString = (String)Properties.Settings.Default.Properties["EggColor"].DefaultValue;
            Color oldColor = ImageUtils.TranslateString(oldColorString);

            Image[] images = new Image[2];
            images[0] = ImageUtils.ChangeColor(SemenQuest.Properties.Resources.Egg1, oldColor, color);
            images[1] = (Image)images[0].Clone();
            images[1].RotateFlip(RotateFlipType.RotateNoneFlipX);

            return images;
        }
        #endregion

        #region Overrides
        protected override void KillAgent(Agent.KillReasons killReason)
        {
            SettingsController.EggSettingChanged -= Agent_SettingsUpdate;

            base.KillAgent(killReason);
        }
        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                cp.ExStyle |= 0x20;
                return cp;
                //return base.CreateParams;
            }
        }
        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
        {
            if (Angle > 90 || Angle < -90)
            {
                e.Graphics.DrawImage(AnimationImages[0], this.DisplayRectangle);
            }
            else
            {
                e.Graphics.DrawImage(AnimationImages[1], this.DisplayRectangle);
            }
        }
        #endregion

        #region Communications
        [MethodImpl(MethodImplOptions.Synchronized)]
        private void HandleMessage(Conversation conversation)
        {
            Communication.Message message = conversation.ReturnNewest();
            if (message == null)
            {
                return;
            }

            switch (message.Type)
            {
                case Communication.Message.MessageTypes.Request:
                    if (message.Content == Communication.Message.Contents.Impregnate)
                    {
                        Agent semenContender = AgentController.GetAgentCount().SingleOrDefault(a => a.Id == message.Sender);
                        if (semenContender != null)
                        {
                            Int32 impregnatorCount = 0;
                            Boolean isImpregnating = false;
                            lock (Impregnators)
                            {
                                impregnatorCount = Impregnators.Count;
                                isImpregnating = Impregnators.Exists(x => x.Id == semenContender.Id);
                            }

                            if (impregnatorCount < MaxImpregnators && !isImpregnating)
                            {
                                Random random = new Random();
                                Int32 impregNr = random.Next(0, MaxImpregnators);
                                lock (Impregnators)
                                {
                                    while (ImpregLocations[impregNr]) impregNr = random.Next(0, MaxImpregnators);
                                    ImpregLocations[impregNr] = true;
                                    Impregnators.Add(new Impregnator(semenContender.Id, impregNr));
                                }

                                SendMessage(semenContender, new Communication.Message(
                                   this.Id,
                                   semenContender.Id,
                                   Communication.Message.MessageTypes.Agree,
                                   Communication.Message.Contents.Impregnate,
                                   impregNr));
                            }
                            else
                            {
                                SendMessage(semenContender, new Communication.Message(
                                    this.Id,
                                    semenContender.Id,
                                    Communication.Message.MessageTypes.Refuse,
                                    Communication.Message.Contents.Impregnate));
                            }
                        }
                    }
                    break;


                default:
                    Agent semen = AgentController.GetNearbyAgents(this).SingleOrDefault(a => a.Id == message.Sender);

                    if (semen != null)
                    {
                        SendMessage(semen, new Communication.Message(
                            this.Id,
                            semen.Id,
                            Communication.Message.MessageTypes.NotUnderstood,
                            Communication.Message.Contents.Nothing));
                    }

                    break;

            }

        }
        #endregion

        #region Methods
        [MethodImpl(MethodImplOptions.Synchronized)]
        private void Main(Object sender, ElapsedEventArgs e)
        {
            lock (Impregnators)
            {
                //Add lottery tickets, and remove impregnators, who has swim away
                var agents = AgentController.GetNearbyAgents(this);
                for (Int32 i = 0; i < Impregnators.Count; )
                {
                    if (agents.Exists(x => x.Id == Impregnators[i].Id))
                    {
                        Impregnators[i].LotteryNumbers++;
                        i++;
                    }
                    else
                    {
                        ImpregLocations[Impregnators[i].Position] = false;
                        Impregnators.RemoveAt(i);
                    }
                }
            }
            if (!LotteryStarted && SemenAttached == ImpregnatorsNeeded) //Enough impregnators, impregnation success!
            {
                LotteryTimer.Start();
            }
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private new void Move(Object sender, ElapsedEventArgs e)
        {
            #region New Location and Angle
            Point newLocation = MathUtils.NewLocation(Location, Angle, Velocity / 10);
            try
            {
                Int32 distance = ImpregLocations[0] ? 35 : 0; //semen attached to the left, increase bounce distance to avoid stuck semen                
                //Wall bounce left and right
                if (newLocation.X <= distance)
                {
                    Angle = (int)MathUtils.HorizontalAngleOut(newLocation, Location);
                    newLocation = MathUtils.NewLocation(Location, Angle, Velocity / 10);
                    while (newLocation.X <= distance) newLocation.X++;
                }
                distance = ImpregLocations[2] ? 35 : 0;
                if (this.Parent.Size.Width <= newLocation.X + Size.Width + distance)
                {
                    Angle = (int)MathUtils.HorizontalAngleOut(newLocation, Location);
                    newLocation = MathUtils.NewLocation(Location, Angle, Velocity / 10);
                    while (this.Parent.Size.Width <= newLocation.X + Size.Width + distance) newLocation.X--;
                }

                //Wall bounce top and bottom
                distance = ImpregLocations[3] ? 35 : 0;
                if (newLocation.Y <= distance)
                {

                    Angle = (int)MathUtils.VerticalAngleOut(newLocation, Location);
                    newLocation = MathUtils.NewLocation(Location, Angle, Velocity / 10);
                    while (newLocation.Y <= distance) newLocation.Y++;
                }
                distance = ImpregLocations[1] ? 35 : 0;
                if (this.Parent.Size.Height <= newLocation.Y + Size.Height + distance)
                {
                    Angle = (int)MathUtils.VerticalAngleOut(newLocation, Location);
                    newLocation = MathUtils.NewLocation(Location, Angle, Velocity / 10);
                    while (this.Parent.Size.Height <= newLocation.Y + Size.Height + distance) newLocation.Y--;
                }
            }
            catch { }


            try
            {
                this.BeginInvoke(new Action(delegate
                {
                    if (!this.IsDisposed)
                    {
                        this.Location = newLocation;
                    }
                }));
            }
            catch { }

            #endregion
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private void LotteryDone(Object sender, ElapsedEventArgs e)
        {
            //Time to find the winner, drawn as in lottery
            List<Agent> agents = AgentController.GetAgentCount();
            List<Impregnator> impregs;
            lock (Impregnators)
            {
                impregs = Impregnators.ToList();
            }

            //Check that all agents still live and calculate the sum of lottery numbers
            Int32 LotterySum = 0;
            for (Int32 i = 0; i < impregs.Count; )
            {
                if (agents.Exists(x => x.Id == impregs[i].Id))
                {
                    LotterySum += impregs[i].LotteryNumbers;
                    i++;
                }
                else
                {
                    impregs.RemoveAt(i);
                }
            }

            if (impregs.Count != 0)
            {
                //draw the lucky number
                Random r = new Random();
                Int32 winnerTicket = r.Next(1, LotterySum);

                //Find the winner
                Int32 counterToWinnerTicket = 0;
                foreach (Impregnator impregnator in impregs)
                {
                    //if impregnator is the winner?
                    if (winnerTicket <= counterToWinnerTicket + impregnator.LotteryNumbers)
                    {
                        Agent agent = agents.Find(x => x.Id == impregnator.Id);
                        if (agent != null)
                        {
                            SendMessage(agent, new Communication.Message(
                                this.Id,
                                impregnator.Id,
                                Communication.Message.MessageTypes.Inform,
                                Communication.Message.Contents.YouImpregnatedTheEgg));
                        }
                    }
                    else //The Loosers
                    {
                        counterToWinnerTicket += impregnator.LotteryNumbers;
                        Agent agent = agents.Find(x => x.Id == impregnator.Id);
                        if (agent != null)
                        {
                            SendMessage(agent, new Communication.Message(
                                this.Id,
                                impregnator.Id,
                                Communication.Message.MessageTypes.Inform,
                                Communication.Message.Contents.YouDidNotImpregnateTheEgg));
                        }
                    }
                }
            }
            KillAgent(KillReasons.LotteryWon);
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        protected override void LifeEnded()
        {
            LotteryTimer.Stop();
            var agents = AgentController.GetAgentCount();
            foreach (Impregnator semen in Impregnators)
            {
                var agent = agents.Find(x => x.Id == semen.Id);
                if (agent != null)
                    agent.RecordMessage(new Communication.Message(
                        this.Id,
                        agent.Id,
                        Communication.Message.MessageTypes.Inform,
                        Communication.Message.Contents.YouDidNotImpregnateTheEgg));
            }
            KillAgent(KillReasons.LifeTimeEnded);
        }
        #endregion
    }
}
